#include "../include/Window.h"
//#include "../include/WindowException.h"
#include "../include/Renderer.h"
#include "../include/Texture.h"
#include <SDL2/SDL.h>
#include <stdexcept>
#include <iostream>
#include <string>
#include <assert.h>

#include <new>
#include "../include/angelscript.h"
#include "../anglescript/scriptstdstring/scriptstdstring.h"
#include "../anglescript/scriptbuilder/scriptbuilder.h"
#include "../anglescript/autowrapper/aswrappedcall.h"

namespace GUL {
    namespace System {
        namespace Graphic {
            
            Window* FactoryWindow(const std::string& Name, int x,int y,int w,int h)
            {
                return new Window(Name,x,y,w,h,SDL_WINDOW_SHOWN);
            }
            
            void WindowCtor(void* mem)
            {
                new (mem) Window();
            }
            
            void Window::AddScriptEngine(asIScriptEngine* engine)
            {
                int r=0;
                r = engine->RegisterObjectType("Window",0,asOBJ_REF); assert( r >= 0);
                r = engine->RegisterObjectBehaviour("Window",asBEHAVE_FACTORY,"Window @f(string name,int x,int y,int w,int h)",asFUNCTION(FactoryWindow),asCALL_CDECL) ;assert ( r >= 0 );


                r = engine->RegisterObjectBehaviour("Window",asBEHAVE_ADDREF,"void f()",asMETHOD(Window,AddRef),asCALL_THISCALL); assert ( r >= 0);
                r = engine->RegisterObjectBehaviour("Window",asBEHAVE_RELEASE,"void f()",asMETHOD(Window,ReleaseRef),asCALL_THISCALL); assert ( r >= 0);
                r = engine->RegisterObjectMethod("Window","void Clean()",asMETHOD(Window,Clean),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Update()",asMETHOD(Window,Update),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","bool IsValid()",asMETHOD(Window,IsValid),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void SetValid(bool val)",asMETHOD(Window,SetValid),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void HandleInput()",asMETHOD(Window,HandleInput),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void InitSDLRenderSupport()",asMETHOD(Window,InitSDLRenderSupport),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","bool IsSDLRenderSupportEnabled()",asMETHOD(Window,IsSDLRenderSupportEnabled),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","uint ID()",asMETHOD(Window,ID),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","uint Flags()",asMETHOD(Window,Flags),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","string Title()",asMETHOD(Window,getTitle),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Show()",asMETHOD(Window,Show),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Hide()",asMETHOD(Window,Hide),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Rise()",asMETHOD(Window,Rise),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Maximize()",asMETHOD(Window,Maximize),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Minimize()",asMETHOD(Window,Minimize),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Restore()",asMETHOD(Window,Restore),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void Fullscreen()",asMETHOD(Window,Fullscreen),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void EnableScreenSaver()",asMETHOD(Window,EnableScreenSaver),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("Window","void DisableScreenSaver()",asMETHOD(Window,DisableScreenSaver),asCALL_THISCALL); assert (r >= 0);


            }


            Window::Window(const std::string& Name, IPair Pos, IPair Boundary, Uint32 flags) : m_Display(NULL), m_RenderManager(NULL) {
                this->m_Display = SDL_CreateWindow(Name.c_str(), Pos.first, Pos.second, Boundary.first, Boundary.second, flags);
                if (this->m_Display == nullptr)
                    throw std::runtime_error("Couldn't create valid Display. SDL Error: " + std::string( SDL_GetError()));
                //////////////////////////////////////////////////////////////////////////
                this->m_valid = true;
                this->m_borderd = true;
                this->m_Texture = false;
                                this->m_referenceCount = 1;

                //////////////////////////////////////////////////////////////////////////
            }
                       
            Window::Window(const std::string& Name, int x, int y, int w, int h, Uint32 flags) : m_Display(NULL), m_RenderManager(NULL)  {

                            this->m_Display = SDL_CreateWindow(Name.c_str(), x, y, w, w, flags);
                if (this->m_Display == nullptr)
                    throw std::runtime_error("Couldn't create valid Display. SDL Error: " + std::string( SDL_GetError()));
                //////////////////////////////////////////////////////////////////////////
                this->m_valid = true;
                this->m_borderd = true;
                this->m_Texture = false;
                                this->m_referenceCount = 1;

                //////////////////////////////////////////////////////////////////////////
            }


            Window::~Window() {
                if (this->IsValid())
                    SDL_DestroyWindow(this->m_Display);
            }

            Renderer& Window::operator ()() {
                if (this->m_RenderManager == nullptr)
                    throw std::logic_error("RenderManager is not initialized. Did you forgot to use Window::InitSDLRenderSupport()?\n" + std::string(SDL_GetError()));
                return *this->m_RenderManager;
            }

            void Window::operator ()(Texture& tex, SDL_Rect* src, SDL_Rect* dst) {
                this->m_RenderManager->RenderCopy(tex, src, dst);
            }

            Texture& Window::operator ()(const std::string& name) {
                return this->m_RenderManager->operator [](name);
            }

            void Window::InitSDLRenderSupport() {
                this->m_RenderManager = new Renderer(*this, SDL_RENDERER_ACCELERATED);
                if (this->m_RenderManager == nullptr)
                    throw std::runtime_error("Not enough Memory avaible\n");
                this->m_Texture = true;
            }

            bool Window::IsSDLRenderSupportEnabled() {
                return this->m_Texture;
            }

            void Window::ShutdownSDLRenderSupport() {
                this->m_RenderManager->~Renderer();
                this->m_RenderManager = nullptr;
                this->m_Texture = false;
            }

            Renderer& Window::getRenderer() {
                return *this->m_RenderManager;
            }

            SDL_Surface* Window::Surface() {
                if (!this->m_Texture) {
                    SDL_Surface* surf = nullptr;
                    surf = SDL_GetWindowSurface(this->m_Display);
                    if (surf != nullptr)
                        return surf;
                    else
                        throw std::runtime_error("Error Couldn't Create Surface from Window at Function Surface(). SDL Error: " + std::string(SDL_GetError()));
                } else {
                    return 0;
                }
            }

            Uint32 Window::ID() {
                return SDL_GetWindowID(this->m_Display);
            }

            Uint32 Window::Flags() {
                return SDL_GetWindowFlags(this->m_Display);
            }

            std::string Window::getTitle() {
                return SDL_GetWindowTitle(this->m_Display);
            }

            UPair Window::Position() {
                int x, y;
                SDL_GetWindowPosition(this->m_Display, &x, &y);
                return UPair(x, y);
            }

            UPair& Window::Size() {
                int w, h;
                SDL_GetWindowSize(this->m_Display, &w, &h);
                UPair* tmp = new UPair(w, h);
                if (tmp == nullptr)
                    throw std::runtime_error("Not enough Memory avaiable\n");
                return *tmp;
            }

            //    void Window::DisplayBounds(SDL_Rect* rect)
            //    {
            //         SDL_GetDisplayBounds(this->DisplayIndex(),rect);
            //    }
            //    
            //    int Window::DisplayIndex()
            //    {
            //        return SDL_GetWindowDisplayIndex(this->m_Display);
            //    }

            UPair Window::MaximumSize() {
                int w, h;
                SDL_GetWindowMaximumSize(this->m_Display, &w, &h);
                return UPair(w, h);
            }

            UPair Window::MinimumSize() {
                int w, h;
                SDL_GetWindowMaximumSize(this->m_Display, &w, &h);
                return UPair(w, h);
            }

            void Window::MaximumSize(UPair val) {
                SDL_SetWindowMaximumSize(this->m_Display, val.first, val.second);
            }

            void Window::MinimumSize(UPair val) {
                SDL_SetWindowMinimumSize(this->m_Display, val.first, val.second);
            }

            void Window::Show() {
                SDL_ShowWindow(this->m_Display);
            }

            void Window::Hide() {
                SDL_HideWindow(this->m_Display);
            }

            void Window::Rise() {
                SDL_RaiseWindow(this->m_Display);
            }

            void Window::Maximize() {
                SDL_MaximizeWindow(this->m_Display);
            }

            void Window::Minimize() {
                SDL_MinimizeWindow(this->m_Display);
            }

            void Window::Restore() {
                SDL_RestoreWindow(this->m_Display);
            }

            void Window::Fullscreen() {
                SDL_SetWindowFullscreen(this->m_Display, SDL_WINDOW_FULLSCREEN);
            }

            //    void Window::Windowed()
            //    {
            //         SDL_SetWindowFullscreen(this->m_Display,SDL_WINDOW_FULLSCREEN_DESKTOP);
            //    }

            SDL_bool Window::SetGrab(SDL_bool grab) {
                 SDL_SetWindowGrab(this->m_Display, grab);
                 return grab;
            }

            SDL_bool Window::Grab() {
                return SDL_GetWindowGrab(this->m_Display);
            }

            SDL_bool Window::IsScreenSaverEnabled() {
                return SDL_IsScreenSaverEnabled();
            }

            void Window::EnableScreenSaver() {
                SDL_EnableScreenSaver();
            }

            void Window::DisableScreenSaver() {
                SDL_DisableScreenSaver();
            }

            float Window::Brightness() {
                return SDL_GetWindowBrightness(this->m_Display);
            }

            void Window::Brightness(float bright) {
                SDL_SetWindowBrightness(m_Display, bright);
            }

            std::vector<Uint16*>& Window::GetGammaRamp(Uint16*r,Uint16*g,Uint16*b) {
                
                std::vector<Uint16*> *tmp = new std::vector<Uint16*>;
                SDL_GetWindowGammaRamp(this->m_Display, r, g, b);
                tmp->push_back(r);
                tmp->push_back(g);
                tmp->push_back(b);
                return *tmp;
            }

            void Window::SetGammaRamp(Uint16*r,Uint16*g,Uint16*b) {
                SDL_SetWindowGammaRamp(this->m_Display, r, g, b);
            }

            void* Window::Data() {
                return SDL_GetWindowData(this->m_Display, this->getTitle().c_str());
            }

            void Window::Data(void* data) {
                SDL_SetWindowData(this->m_Display, this->getTitle().c_str(), data);
            }

            void Window::Clean() {
                if (!this->m_Texture) {
                    SDL_Surface* screenSurface = nullptr;
                    screenSurface = this->Surface();
                    //Fill the surface white
                    SDL_FillRect(screenSurface, nullptr, SDL_MapRGB(screenSurface->format, 0x00, 0x00, 0x0f));
                } else {
                    this->m_RenderManager->Clear();
                }
            }

            void Window::Update() {
                //Update the surface

                if (!this->m_Texture) {
                    SDL_UpdateWindowSurface(this->m_Display);
                } else {
                    this->m_RenderManager->Present();
                }
            }

            void Window::Bordered() {
                if (this->m_borderd == true) {
                    SDL_SetWindowBordered(this->m_Display, SDL_FALSE);
                    this->m_borderd = false;
                } else {
                    SDL_SetWindowBordered(this->m_Display, SDL_TRUE);
                    this->m_borderd = true;
                }


            }

            void Window::HandleInput() {
                
//                if (e == nullptr)
//                    throw std::invalid_argument("Window::HandleInput: SDL_Event have to be not a nullptr\n");
                //Handle events on queue
                while (SDL_PollEvent(&e) != 0) {
                    //User requests quit
                    if (e.type == SDL_QUIT)
                    {
                        this->SetValid(false);
                    }
                    if (SDL_GetKeyFromScancode(e.key.keysym.scancode) == SDLK_ESCAPE)
                    {
                        this->SetValid(false);
                    }
#ifdef USE_CEGUI
                    if(e.type == SDL_KEYDOWN)
                    {
                       
                    }
#endif

                }
            }

            bool Window::IsValid() {
                //        if(this->m_Display != NULL)
                //            this->SetValid(true);
                return this->m_valid;
            }

            void Window::SetValid(bool val) {
                this->m_valid = val;
            }
        }
    }

}